package lspServer;


import java.io.Closeable;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import lspUtils.LspConnection;
import lspUtils.LspPack;
import lspUtils.LspPacket;
import lspUtils.LspParams;
import lspUtils.MessageType;




public class LspServer implements Closeable {
	
	private Map<Short, LspConnection> connectionTable = new HashMap<>();
	
	private DatagramSocket socket;
	private LspParams lspParams;
	private LinkedBlockingQueue<LspPack> receivedPack = new LinkedBlockingQueue<>();
	
	private Thread connectionThreadManager;
	
	public LspServer(int port, LspParams lspParams) throws SocketException {
		this.socket = new DatagramSocket(port);
		this.lspParams = lspParams;
		
		startConnectionManager();
	}
	
	private void startConnectionManager() {
		ConnectionManager manager = new ConnectionManager();
		connectionThreadManager = new Thread(manager);
		connectionThreadManager.start();
	}

	
	public LspPack read() {
		while (receivedPack.isEmpty());
		return receivedPack.poll();
	}

	
	public void write(LspPack pack) {
		LspConnection lspConnection = connectionTable.get(pack.getConnId());
		lspConnection.sendData(pack.getPayload());
	}

	
	public void closeConn(short connId) throws IOException {
		LspConnection connection = connectionTable.remove(connId);
		connection.close();
	}
	
	
	public void close() throws IOException {
		for (short connId: connectionTable.keySet()) {
			closeConn(connId);
		}
		connectionThreadManager.interrupt();
		socket.close();
	}
	
	public int numConnections() {
		return connectionTable.size();
	}
	
	public Map<Short, LspConnection> getConnectionTable() {
		return connectionTable;
	}

	class ConnectionManager implements Runnable {

		private short currentConnectionId = 0;
		
		private short nextConnectionId() {
			return ++currentConnectionId;
		}	
		
		private void acceptNewClient(InetAddress inetAddress) {
			Short id = nextConnectionId();
			try {
				LspConnection connection = new LspConnection(inetAddress, 4999, id, lspParams);
				connectionTable.put(id, connection);
			} catch (SocketException e) {
				e.printStackTrace();
			}
		}

		
		public void run() {
			byte[] buf = new byte[1000];
			while (true) {
				DatagramPacket packet = new DatagramPacket(buf, buf.length);
				try {
					socket.receive(packet);
					
					LspPacket lspPacket = LspPacket.fromDatagramPacket(packet);
					if (lspPacket.getMessageType() == MessageType.CONNECT) {
						acceptNewClient(packet.getAddress());
					} else {
						LspConnection lspConnection = connectionTable.get(lspPacket.getConnId());
						lspConnection.addReceivedExternalPack(lspPacket);
						
					}
				} catch (SocketException e) {
					// socket closed: terminate execution
					return;
				} catch (IOException e) {
					e.printStackTrace();
				}

				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					// interrupted: terminate execution
					return;
				}
			}
		}
	}
	
	

}
